<?php

namespace CommonBundle\Controller;

use CommonBundle\Utils\ArrayCommon;
use CommonBundle\Utils\FixJSON;
use CommonBundle\Utils\Math;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\QueryBuilder;
use Knp\Component\Pager\Pagination\AbstractPagination;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\HttpFoundation\Response;

class RestController extends Controller
{
    const UNKNOWN_ERROR = 'Operation failure, please contact administrator';

    /**
     * @param $collection
     * @return array|ArrayCollection|QueryBuilder
     */
    protected function pagination($collection)
    {
        // get current request
        $request_stack = $this->get('request_stack');
        $request = $request_stack->getCurrentRequest();

        $DEFAULT_PAGE_LIMIT = 100; // PHP_INT_MAX

        if ($collection && (
                is_array($collection)
                || $collection instanceof ArrayCollection
                || $collection instanceof QueryBuilder
            )) {
            $pager = $this->get('knp_paginator');

            if ($request->getMethod() === 'GET') {
                // GET
                $paginated = $pager->paginate($collection,
                    $request->query->getInt('page', 1),
                    $request->query->getInt('limit', $DEFAULT_PAGE_LIMIT));
            } else {
                $paginated = $collection;
            }
            return $paginated;
        } else {
            return $collection;
        }
    }

    /**
     * @param $entity
     * @param array $attributeSets
     */
    private function expandObjects($entity, array $attributeSets)
    {
        foreach ($attributeSets as $attributeSet) {
            $attributeChain = explode('.', $attributeSet);

            if (current($attributeChain) == '' || current($attributeChain) == 'entity') {
                array_shift($attributeChain);
            }
            $this->expandObjectToMetadata($entity, $attributeChain);
        }
    }

    /**
     * @param $entity
     * @param array $attributeChain
     * @param int $level
     */
    private function expandObjectToMetadata(&$entity, array $attributeChain, int $level = -1)
    {
        if (empty($entity) || 0 === count($attributeChain) || 0 === $level) return;

        if (method_exists($entity, $getter = 'get' . ucfirst(trim($attributeChain[0])))) {
            if ($next = $entity->$getter()) {
                foreach ($next instanceof \Traversable ? $next : [$next] as $node) {
                    // expand
                    $node->__metadata = $node;

                    // recursive
                    $copy = $attributeChain;
                    $this->expandObjectToMetadata(
                        $node,
                        array_splice($copy, 1),
                        $level - 1
                    );
                }
            }
        }
    }

    /**
     * @param $collection
     * @return array|array[]|ArrayCollection|mixed
     */
    private function requestProcess($collection)
    {
        $request_stack = $this->container->get('request_stack');
        $request = $request_stack->getCurrentRequest();

        // Expend Object
        $expands = json_decode(
            str_replace('\'', '"',
                $request->query->get('@expands', '[]')), true);
        try {
            if (is_array($expands)) {
                if ($collection && (
                        is_array($collection)
                        || $collection instanceof ArrayCollection)
                ) {
                    foreach ($collection as $entity) {
                        $this->expandObjects($entity, $expands);
                    }
                } else {
                    $this->expandObjects($collection, $expands);
                }
            }

        } catch (\Exception $exception) {
        }

        // General display
        if ($collection && (
                is_array($collection)
                || $collection instanceof ArrayCollection)
        ) {
            $display = $request->query->get('@display', 'complex');
            $displayRequest = FixJSON::fixJSON($display);
            $display = json_decode($displayRequest) ?? $display;

            if (is_array($display)) {
                return array_map(function ($entity) use ($display) {
                    $result = [];
                    foreach ($display as $part) {
                        $part = trim($part);
                        $fields = explode('.', $part);
                        if (current($fields) == '' || current($fields) == 'entity') {
                            array_shift($fields);
                        }

                        $next = $entity;
                        foreach ($fields as $field) {
                            $fieldGetter = 'get' . ucfirst($field);
                            $next = $next->$fieldGetter();
                        }

                        $result[$part] = $next;
                    }

                    return $result;
                }, $collection);
            } elseif (is_object($display)) {
                $display = json_decode($displayRequest, true) ?? $display;
                $result = [];

                foreach ($collection as $item) {
                    $set = [];
                    foreach ($display as $key => $value) {
                        try {
                            $expressionLanguage = new ExpressionLanguage();

                            try {
                                // try to inject item service class
                                $className = get_class($item);
                                $serviceClassName = str_replace('Entity', 'Service', $className) . 'Service';
                                $service = $this->get($serviceClassName);
                                $values = $service->externalExpressionValues();
                                $values['entity'] = $item;
                            } catch (\Exception $exception) {
                                $values = [
                                    'entity' => $item,
                                    'Math' => new Math(),
                                    'ArrayCommon' => new ArrayCommon()
                                ];
                            }

                            $set[$key] = $expressionLanguage->evaluate(
                                $value, $values
                            );
                        } catch (\Exception $e) {
                        }
                    }
                    $result[] = $set;
                }

                return $result;
            } else {
                if ($display === 'reduce') {
                    return array_map(function ($entity) {
                        return [
                            'id' => $entity->getId(),
                            '__toString' => $entity->__toString(),
                        ];
                    }, $collection);
                } else {
                    return $collection;
                }
            }
        }

        return $collection;
    }

    /**
     * @param string $content
     * @param string $addition_message
     * @return Response
     */
    protected function Success($content = '', $addition_message = 'SUCCESS'): Response
    {
        $serializer = $this->get("serializer");
        $paginatedContent = $this->pagination($content);

        if ($paginatedContent instanceof AbstractPagination) {
            $processedContent = $this->requestProcess($paginatedContent->getItems());
        } else {
            $processedContent = $this->requestProcess($paginatedContent);
        }

        $response = [
            'data' => $processedContent,
            'code' => 0,
            'message' => $addition_message,
        ];
        if ($paginatedContent instanceof AbstractPagination) {
            $response['paginator'] = $paginatedContent->getPaginationData();
        }
        return new Response($serializer->serialize($response, 'json'), 200, array());
    }

    /**
     * @param string $error_msg
     * @param int $error_code
     * @param string $raw_data
     * @return Response
     */
    protected function Warning($error_msg = self::UNKNOWN_ERROR, $error_code = -1, $raw_data = ''): Response
    {
        $serializer = $this->get("serializer");
        $response = [
            'code' => $error_code,
            'message' => $this->get('translator')->trans($error_msg),
            'raw_data' => $raw_data,
        ];
        return new Response($serializer->serialize($response, 'json'), 200, array());
    }

}
